using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.Word;

namespace WordMacro
{
    class Word
    {
        private Application _application;

        public Word()
        {
            _application = new ApplicationClass();
        }

        #region IDisposable Members
        ~Word()
        {
            Dispose(false);
        }

        private bool _disposed = false;

        public Application Application
        {
            get
            {
                VerifyNotDisposed();
                return this._application;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this._disposed)
            {
                if (disposing)
                {
                    // dispose-only, i.e. non-finalizable logic

                }
                this._disposed = true;
                // shared (dispose and finalizable) cleanup logic
                if (_application != null)
                {
                    object falseObj = false;
                    object nullObj = Type.Missing;
                    ((_Application)_application).Quit(ref falseObj /*save Changes*/,
                                          ref nullObj /*original format*/,
                                          ref nullObj /*route document*/);
                    //                Marshal.ReleaseComObject(_application);
                    _application = null;
                }
            }
        }

        protected void VerifyNotDisposed()
        {
            if (this._disposed)
            {
                throw new ObjectDisposedException("WordApplication");
            }
        }
        #endregion
    }

    class Program
    {
        private static Word _word;

        static int Main(string[] args)
        {
            int errorCode = 0;
            if (args.Length < 5)
            {
                WriteUsage();
                errorCode = 1;
            }

            Document document = null;
            if (errorCode == 0)
            {
                _word = new Word();
                try
                {
                    document = OpenDocument(args[0]);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unable to open input file " + args[0]);
                    Console.WriteLine("Details:");
                    Console.WriteLine(e.Message + '\n' + e);
                    errorCode = 2;
                }
            }

            if (errorCode == 0 && document != null)
            {
                try
                {
                    object templateName = Path.GetFullPath(args[3]); 
                    document.set_AttachedTemplate(ref templateName);
                    document.Activate();
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unable to open template file " + args[2]);
                    Console.WriteLine("Details:");
                    Console.WriteLine(e.Message + '\n' + e);
                    errorCode = 4;
                }
            }

            if (errorCode == 0)
            {
                Object[] macroArgs = new Object[args.Length - 4];
                    
                try
                {
                    Array.Copy(args, 4, macroArgs, 0, args.Length - 4);
                    RunMacro(macroArgs);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unable to run macro with args:" );
                    foreach (object o in macroArgs)
                    {
                        Console.WriteLine("\t" + o);
                    }
                    Console.WriteLine("Details:");
                    Console.WriteLine(e.Message + '\n' + e);
                    errorCode = 5;
                }
            }


            if (errorCode == 0)
            {
                try
                {
                    foreach (Document d in _word.Application.Documents)
                    {
                        SaveDocumentAs(d, args[1], args[2]);
                        break;
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unable to save output file " + args[1]);
                    Console.WriteLine("Details:");
                    Console.WriteLine(e.Message);
                    errorCode = 3;
                }
                finally
                {
                    CloseAllDocuments();
                }
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            GC.Collect();
            GC.WaitForPendingFinalizers();
            return errorCode;
        }

        private static void RunMacro(object[] macroArgs)
        {
            _word.Application.GetType().InvokeMember("Run",
                                         BindingFlags.Default |
                                         BindingFlags.InvokeMethod,
                                         null,
                                         _word.Application,
                                         macroArgs);
        }

        private static Document OpenDocument(string fileName)
        {
            object fileNameObj = Path.GetFullPath(fileName);
            object trueObj = true;
            object falseObj = false;
            object nullObj = Type.Missing;
            return _word.Application.Documents.Open(ref fileNameObj,
                                        ref falseObj  /*confirm conversions*/,
                                        ref falseObj /*readonly*/,
                                        ref falseObj /*add to recent files*/,
                                        ref nullObj /*password for opening the document*/,
                                        ref nullObj /*password for opening the template*/,
                                        ref nullObj /*revert changes if already open*/,
                                        ref nullObj /*password for saving the document*/,
                                        ref nullObj /*password for saving the template*/,
                                        ref nullObj /*file converter*/,
                                        ref nullObj /*encoding*/,
                                        ref falseObj /*visible*/,
                                        ref falseObj /*repair*/,
                                        ref nullObj /*document direction*/,
                                        ref trueObj /*don't show encoding dialog*/,
                                        ref nullObj /*xml transform to use*/);
        }

        private static void CloseAllDocuments()
        {
            object falseObj = false;
            object nullObj = Type.Missing;
            _word.Application.Documents.Close(ref falseObj/*save changes*/,
                                               ref nullObj/*original format*/,
                                               ref nullObj/*route document*/);
        }

        private static void SaveDocumentAs(Document document, string fileName, string format)
        {
            string saveFormatAsString = "wdFormat" + format;
            WdSaveFormat saveFormat;
            try
            {
                saveFormat = (WdSaveFormat)Enum.Parse(typeof(WdSaveFormat), saveFormatAsString);
            }
            catch(ArgumentException)
            {
                string errorMessage = "Bad Format Value. Must be one of:\n";
                foreach (string s in GetAvailableFormats())
                {
                    errorMessage += '\t' + s +'\n';
                }
                throw new ArgumentException(errorMessage);
            }


            object fileNameObj = Path.GetFullPath(fileName);
            object saveFormatObj = saveFormat;
            object falseObj = false;
            object nullObj = Type.Missing;
            object encodingObj = MsoEncoding.msoEncodingUnicodeLittleEndian;

            document.SaveAs(ref fileNameObj,
                ref saveFormatObj,/*file format*/
                ref falseObj/*lock comments*/,
                ref nullObj/*password*/,
                ref falseObj/*add to recent*/,
                ref nullObj/*write password*/,
                ref nullObj/*read only recommended*/,
                ref nullObj/*embed true-type fonts*/,
                ref nullObj/*save native picture format*/,
                ref nullObj/*save forms data*/,
                ref nullObj/*save as AOCE Letter*/,
                ref encodingObj/*encoding*/,
                ref nullObj/*insert line breaks*/,
                ref nullObj/*allow substitutions*/,
                ref nullObj/*line ending*/,
                ref nullObj/*add bidi marks*/);
        }

        private static void WriteUsage()
        {
            Console.WriteLine("Usage: WordMacro Input Output Format Template Module.Macro [Macro args]");
            Console.WriteLine("Available formats:");

            foreach (string format in GetAvailableFormats())
            {
                Console.WriteLine('\t' + format);
            }
        }

        private static IList<string> GetAvailableFormats()
        {
            List<string> formats = new List<string>();
            foreach (string saveFormat in Enum.GetNames(typeof(WdSaveFormat)))
            {
                formats.Add(saveFormat.Substring(8));
            }
            return formats;
        }

    }
}
